#include <Ape/Uuid.h>

#include <Ape/String.h>
#include <Ape/ULong.h>
#include <Ape/ENotImplemented.h>
#include <Ape/Exception.h>

#include <string>

#if defined(APE_UUID_USES_DEV_URANDOM)
#elif defined(APE_UUID_USES_RAND_FUNC)

#include <Ape/MT/Mutex.h>
using Ape::MT::Mutex;
using Ape::MT::MutexLock;

int getRandSeed() {
	static bool firstTime = true;
	static Mutex m;
	static int called;
	MutexLock l (m);
	if (firstTime) {
		firstTime = false;

	}
	called++;
	int seed = time (NULL) + called;
	return seed;
}

int getProcessSeed() {
	static Mutex m;
	static int called = 0;
	MutexLock l (m);
	return (getpid() + called++);
}
int getTimeSeed() {
	static Mutex m;
	static int called = 0;
	MutexLock l (m);
	return (time (NULL) + called++);
}
int getMacSeed() { // STUB
	static Mutex m;
	static int called = 0;
	MutexLock l (m);
	return called++;
}
int getComboSeed() {
	return (getProcessSeed() + getTimeSeed() + getMacSeed() );
}
#else

#endif


namespace Ape {
	static std::string Uuid_DevRandom ("/dev/urandom");
	static std::string Uuid_DevURandom ("/dev/urandom");
	
	Uuid::Uuid() {
		Reset();
	}
	Uuid::Uuid (const Uuid& other) : Object (other) {
		PutBuffIntoContainer (m_1, other.m_1, 4u);
		PutBuffIntoContainer (m_2, other.m_2, 2u);
		PutBuffIntoContainer (m_3, other.m_3, 2u);
		PutBuffIntoContainer (m_4, other.m_4, 2u);
		PutBuffIntoContainer (m_5, other.m_5, 6u);
	}
	Uuid::Uuid (const char bytes[16]) {
		Reset();
		PutBuffIntoContainer (m_1, bytes, 4u);
		PutBuffIntoContainer (m_2, bytes + 4, 2u);
		PutBuffIntoContainer (m_3, bytes + 6, 2u);
		PutBuffIntoContainer (m_4, bytes + 8, 2u);
		PutBuffIntoContainer (m_5, bytes + 10, 6u);
	}
	Uuid::Uuid (const String& s) {
		//s._Dump();
		throw Ape::ENotImplemented(L"Implement me!");
	}
	
	
	
	Uuid::~Uuid() {}
	
	void Uuid::Reset() {
		PutBuffIntoContainer (m_1, "\0\0\0\0\0", 4u);
		PutBuffIntoContainer (m_2, "\0\0\0\0\0", 2u);
		PutBuffIntoContainer (m_3, "\0\0\0\0\0", 2u);
		PutBuffIntoContainer (m_4, "\0\0\0\0\0", 2u);
		PutBuffIntoContainer (m_5, "\0\0\0\0\0", 6u);
	}
	
	void Uuid::PutBuffIntoContainer (
		char* container, const char* buff, size_t length)
	{
		for (size_t i = 0; i < length; i++) {
			container[i] = buff[i];
		}
	}
// 	ULong Uuid::Container2Long(const char* container, size_t buffSize) {
// 		unsigned long accum = 0;
// 		for (size_t i = 0; i < buffSize; i++) {
// 			//printf("+");
// 			accum += (container[i] * ULong(256).power(i));
// 		}
//
// 		return accum;
// 	}

	String Uuid::Container2String (const char* container, size_t size) {
		char buff[32] = {0};
		char perChar[16] = {0};
		for (size_t i = 0; i < size; i++) {
			sprintf (perChar, "%X02", container[i]);
			buff[i*2] = perChar[0];
			buff[i*2+1] = perChar[1];
		}
		return buff;
	}
	
	Uuid Uuid::Generate() {
	
#if defined(APE_UUID_USES_DEV_URANDOM)
		char buff[32];
		FILE* devRandom = fopen (Uuid_DevRandom.c_str(), "r");
		char randomChar = fgetc (devRandom);
		fclose (devRandom);
		FILE* devURandom = fopen (Uuid_DevURandom.c_str(), "r");
		int bytesRead = fread (buff, sizeof (char), 16, devURandom);
		fclose (devURandom);
		
		if (bytesRead != 16) {
			throw Ape::Exception (L"Failure in /dev/urandom");
		}
		
		//printf("\n");
		for (int i = 0; i < 16; i++) {
			buff[i] ^= randomChar - i;
			//printf("%c", buff[i] ? '.' : '-');
		}
		//printf("\n");
		
		Uuid u (buff);
		return u;
#elif defined(APE_UUID_USES_RAND_FUNC)
		char buff[32];
		
		int rnd = 0;
		char* cRnd = (char*) & rnd;
		srand (getTimeSeed() );
		rnd = rand();
		buff[0] = cRnd[0];
		buff[1] = cRnd[1];
		buff[2] = cRnd[2];
		buff[3] = cRnd[3];
		
		srand (getProcessSeed() );
		rnd = rand();
		buff[4] = cRnd[2];
		buff[5] = cRnd[3];
		
		srand (getMacSeed() );
		rnd = rand();
		buff[6] = cRnd[2];
		buff[7] = cRnd[3];
		
		srand (getRandSeed() );
		rnd = rand();
		buff[8] = cRnd[2];
		buff[9] = cRnd[3];
		
		srand (getComboSeed() );
		rnd = rand();
		buff[10] = cRnd[0];
		buff[11] = cRnd[1];
		buff[12] = cRnd[2];
		buff[13] = cRnd[3];
		srand (getComboSeed() );
		rnd = rand();
		buff[14] = cRnd[2];
		buff[15] = cRnd[3];
		
		
		Uuid u (buff);
		return u;
#else
#warning "No entropy source defined for Ape::Uuid"
#endif
		
	}
	
	void Uuid::_Dump (bool verbose) const {
		Object::_Dump (verbose);
		
		DUMP_NON_V_TYPE;
	}
	
	const String Uuid::ToString() const {
		/*return
			Container2Long(m_1, 4).ToString(radixHex, 8) + L"-" +
			Container2Long(m_2, 2).ToString(radixHex, 4) + L"-" +
			Container2Long(m_3, 2).ToString(radixHex, 4) + L"-" +
			Container2Long(m_4, 2).ToString(radixHex, 4) + L"-" +
			Container2Long(m_5, 4).ToString(radixHex, 8) + L"-" +
			Container2Long(m_5, 2).ToString(radixHex, 4)
			;*/
		return
			Container2String (m_1, 4) + L"-" +
			Container2String (m_2, 2) + L"-" +
			Container2String (m_3, 2) + L"-" +
			Container2String (m_4, 2) + L"-" +
			Container2String (m_5, 6);
	}
	
	bool operator == (const Uuid& u1, const Uuid& u2) {
#define __UUID_CMP_EQ_CONTAINER(u1, u2, aContainer, iSize) \
	for (size_t i = 0; i < iSize; i++) { \
		if (u1.aContainer[i] != u2.aContainer[i]) \
			return false; \
	}
	
		__UUID_CMP_EQ_CONTAINER (u1, u2, m_1, 4)
		__UUID_CMP_EQ_CONTAINER (u1, u2, m_2, 2)
		__UUID_CMP_EQ_CONTAINER (u1, u2, m_3, 2)
		__UUID_CMP_EQ_CONTAINER (u1, u2, m_4, 2)
		__UUID_CMP_EQ_CONTAINER (u1, u2, m_5, 6)
		
		return true;
	}
	bool operator != (const Uuid& u1, const Uuid& u2) {
		return ! (u1 == u2);
	}
	
	bool operator <  (const Uuid& u1, const Uuid& u2) {
#define __UUID_CMP_LESS_CONTAINER(u1, u2, aContainer, iSize) \
	for (size_t i = 0; i < iSize; i++) { \
		if (u1.aContainer[i] < u2.aContainer[i]) \
			return true; \
		if (u1.aContainer[i] > u2.aContainer[i]) \
			return false; \
	}
	
		__UUID_CMP_LESS_CONTAINER (u1, u2, m_1, 4)
		__UUID_CMP_LESS_CONTAINER (u1, u2, m_2, 2)
		__UUID_CMP_LESS_CONTAINER (u1, u2, m_3, 2)
		__UUID_CMP_LESS_CONTAINER (u1, u2, m_4, 2)
		__UUID_CMP_LESS_CONTAINER (u1, u2, m_5, 6)
		
		return false;
	}
	bool operator >  (const Uuid& u1, const Uuid& u2) {
#define __UUID_CMP_MORE_CONTAINER(u1, u2, aContainer, iSize) \
	for (size_t i = 0; i < iSize; i++) { \
		if (u1.aContainer[i] > u2.aContainer[i]) \
			return true; \
		if (u1.aContainer[i] < u2.aContainer[i]) \
			return false; \
	}
	
		__UUID_CMP_MORE_CONTAINER (u1, u2, m_1, 4)
		__UUID_CMP_MORE_CONTAINER (u1, u2, m_2, 2)
		__UUID_CMP_MORE_CONTAINER (u1, u2, m_3, 2)
		__UUID_CMP_MORE_CONTAINER (u1, u2, m_4, 2)
		__UUID_CMP_MORE_CONTAINER (u1, u2, m_5, 6)
		
		return false;
	}
	
	bool operator <= (const Uuid& u1, const Uuid& u2) {
		return ! (u1 > u2);
	}
	bool operator >= (const Uuid& u1, const Uuid& u2) {
		return ! (u1 < u2);
	}
}
